﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ServiceModel;


namespace frag_boardgame
{
    /// <summary>
    /// This class is the "Game's  world" 
    /// </summary>
    
    public class Game : frag_boardgame.IGame
    {

        private GameData data;
        private Logic logic;
        private Dice dice;
        private UndoManager undoManager;
        private StatisticsTable statisticsTable;
        private bool isCommunication;  //SHOULD BE DELETED.  ONLY FOR PROBLEM WITH SAVE_STATISTICS

        public bool IsCommunication  //SHOULD BE DELETED.  ONLY FOR PROBLEM WITH SAVE_STATISTICS
        {
            set { isCommunication = value; }
        }

        public bool CanUndo()
        {
            return undoManager.CanUndo();
        }
        public bool CanRedo()
        {
            return undoManager.CanRedo();
        }

        /// <summary>
        /// Constructor: it probalbly will get paramters such number of players
        /// and other unique things regarding the game
        /// </summary>
        public Game()
        {
            logic = new Logic();
            dice = new Dice();
            undoManager = new UndoManager();
            loadStatistics();
            isCommunication = false;  //SHOULD BE DELETED.  ONLY FOR PROBLEM WITH SAVE_STATISTICS
        }



        private void loadStatistics()
        {
            if (File.Exists("highscores.dat"))
            {
                using (FileStream fs = new FileStream("highscores.dat", FileMode.Open, FileAccess.Read))
                {
                    statisticsTable = StatisticsTable.Load(fs);
                }
            }
            else
            {
                statisticsTable = new StatisticsTable();
            }
        }

        private void saveStatistics()
        {
            if (!isCommunication)
            {
                using (FileStream fs = new FileStream("highscores.dat", FileMode.Create, FileAccess.Write))
                {
                    statisticsTable.Save(fs);
                }
            }
        }

        public void New(Configuraion conf)
        {
            

            data = new GameData(conf);

            
            //StartTurn();
        }

        /// <summary>
        /// start the actual game, New() must be run beforehand. added to allow walls to the game
        /// (walls need to be added after the game has recieved it's configuration)
        /// </summary>
        public void StartGame()
        {
            if (data != null)
            {
                StartTurn();
            }
            else
            {
            }
        }

        public void Save(System.IO.Stream stream)
        {
            data.Save(stream);
        }

        public void Load(System.IO.Stream stream)
        {
            data = GameData.Load(stream);
        }

        public GameData Data
        {
            get { return data; }
        }

        public StatisticsTable Statistics
        {
            get { return statisticsTable; }
        }

        /// <summary>
        /// Move player to other row
        /// </summary>
        /// <param name="delta"></param>
        public void MovePlayerVertically(int delta, bool isDown)
        {
           
            if (!isDown)
                delta = -delta;
            HandleMove(delta, false);


        }

        /// <summary>
        /// Move player to other column
        /// </summary>
        /// <param name="delta"></param>
        public void MovePlayerHorizontally(int delta, bool isRight) 
        {
            if (!isRight)
                delta = -delta;
            HandleMove(delta, true);
        }

        /// <summary>
        /// Move the player in the specified direction
        /// </summary>
        /// <param name="delta"></param>
        /// <param name="direction"></param>
        public void MovePlayer(int delta, Direction direction)
        {
            switch (direction)
            {
                case Direction.Left:
                    MovePlayerHorizontally(delta, false);
                    break;
                case Direction.Right:
                    MovePlayerHorizontally(delta, true);
                    break;
                case Direction.Up:
                    MovePlayerVertically(delta, false);
                    break;
                case Direction.Down:
                    MovePlayerVertically(delta, true);
                    break;
            }
        }
        
        /// <summary>
        /// This method is called if the player wants to finish the current.
        /// </summary>
        public void FinishCurrentTurn()
        {
            NextTurn();
        }

        private Player current_player
        {
            get { return Data.CurrentPlayer; }
        }

        private Board board
        {
            get { return data.Board; }
        }
            
        //this method handles the movement
        private void HandleMove(int delta, bool isHorizontally)
        {
            int to_row;
            int to_col;
            if (isHorizontally)
            {
                to_row = current_player.Row;
                to_col = current_player.Column + delta;
            }
            else
            {
                to_row = current_player.Row + delta;
                 to_col = current_player.Column;
            }
                        
            //if (logic.CanMoveToSquare(board, current_player,to_row, to_col))
            if (logic.CanMoveToSquare(current_player, to_row, to_col,data))
            {
                Square to_sqr = board.GetSquare(to_row, to_col);
                MovePlayer(current_player,to_sqr);
                current_player.MovesLeft -= Math.Abs(delta);

                if (logic.isTurnOver(current_player)) //next player needs to play
                {
                    NextTurn();
                }
                else
                {
                    try
                    {
                        undoManager.AddSnapshot(data);
                    }
                    catch (InvalidOperationException)
                    {
                        throw;
                    }
  
                    
                }
            }


              

        }

        //Switch the turn to the next player
        private void NextTurn()
        {
            data.current_player = getNextPlayer();
            StartTurn();
        }

        

        private void StartTurn()
        {
            
            current_player.MovesLeft = dice.ThrowAndSum(current_player.Speed);
            current_player.ShotsLeft = (current_player.Accuracy+1) / 2;
            undoManager.Reset(data);
            //if this is an AI player, let the AI object play the turn
       //     if (current_player.Control == Player.Controller.AI) AI.PlayAI(this); 
        }

        //get next player, may return null if no players in the game, returns the same player if there is only one
        private int getNextPlayer()
        {
            ICollection<Player> players = data.GetPlayersList();
            if (data.current_player == -1)
            {
                //there is no current user we are starting the game
                if (players.Count > 0)
                {
                    return 0;
                }
                //no players are in the game yet
                return -1;
            }
            for (int i =0; i < players.Count; i++)
            {
                //players in list are non dead players
                if (data.current_player == i)
                {
                    //the current player is the last player, the next player should be the first player
                    if (i == players.Count -1)
                    {
                        return 0;
                    }
                    return i+1;
                }
            }
            //could happen if the current player is not listed as this games player
            return -1;
        }

        /// <summary>
        /// The Method moves a player from square to square
        /// </summary>
        /// <param name="row">Row</param>
        /// <param name="column">Coloumn</param>
        /// <param name="player">Player to move</param>
        private void MovePlayer(Player player, Square to_sqr)
        {
            Square from_sqr = data.Board.GetSquare(player.Row, player.Column);

            player.Row = to_sqr.Row;
            player.Column = to_sqr.Column;
            to_sqr.player = player;
            from_sqr.player = null;
        }


        public void KillPlayer(Player player)
        {
            if (player.isDead())
            {
                data.Board.GetSquare(player.Row, player.Column).player = null;
            }
        }


        //Why does the game need to return the logic???? the game uses the logic but the user does not need to see it??
        //TODO: DELETE!
        public Logic getLogic()
        {
            return logic;
        }
   

        public ShootingValues Shoot(int targetID)
        {
            bool res = false;
            Player shooter = data.CurrentPlayer;
            Player target= data.GetPlayer(targetID);
            ShootingValues shootingValues = new ShootingValues();
            shootingValues.ShooterName = shooter.Name;
            shootingValues.TargetName = target.Name;
            if (logic.canShoot(shooter, target,data))
            {
                shooter.ShotsLeft--;
                int distance = board.GetDistance(shooter, target);
                int diceRoll = dice.ThrowAndSum(shooter.Accuracy);
                if (diceRoll >= distance)
                {
                    target.getHit(CalculateDamage(target,shootingValues));
                    if (target.isDead())
                    {
                        
                        KillPlayer(target);
                        if (logic.isGameOver(data.GetPlayersList())) //if the game is over
                        {
                            // Add a victory point to the winner
                            statisticsTable.AddPlayerVictory(logic.getWinner(data.GetPlayersList()).Name);
                            saveStatistics();

                            shootingValues.Options = ShootingOptionsEnum.GameOver;
                            
                            return shootingValues;
                        }
                        else //if the game is not over (it will be good when we'll have more than 2 players)
                        {
                            
                            shootingValues.Options = ShootingOptionsEnum.PlayerKilled;
                            return shootingValues;
                        }
                    }
                    res = true;
                }
                else
                {
                    res =  false;
                }
                if (logic.isTurnOver(current_player)) //next player needs to play
                {
                    NextTurn();
                }
                else
                {
                    try
                    {
                        undoManager.AddSnapshot(data);
                    }
                    catch (InvalidOperationException)
                    {
                        throw;
                    }

                }

                if (res)
                    
                    shootingValues.Options = ShootingOptionsEnum.Hit;
                else
                   
                    shootingValues.Options = ShootingOptionsEnum.Miss;
                return shootingValues;
            } //Cant Shoot:
//           
            shootingValues.Options = ShootingOptionsEnum.CantShoot;
            return shootingValues;



            
        }


        private int CalculateDamage(Player target,ShootingValues values)
        {
            int shooterDices = dice.ThrowAndSum(2);
            int targerDices = dice.ThrowAndSum(target.Health);
            values.ShooterDices = shooterDices;
            values.TargetDices = targerDices;
            return shooterDices / targerDices;
            
        }

        public void Undo()
        {
            try
            {

                data = undoManager.Undo();
            }
            catch (InvalidOperationException)
            {
                throw;
            }
        }
        public void Redo()
        {
            try
            {
                data = undoManager.Redo();
            }
            catch (InvalidOperationException)
            {
                throw;
            }
        }

      

    }
}
